// NPRWLock.h

#ifndef _NPRWLock_h_
#define _NPRWLock_h_

#if _MSC_VER > 1000
	#pragma once
#endif

#include "ILocker.h"
#ifdef _DEBUG
#include "QuickMap.h"
#endif

// Read-write lock
class NPRWLock
{
	NPRWLock(const NPRWLock&);
	NPRWLock& operator = (const NPRWLock&);

public:
	NPRWLock();
	~NPRWLock();

	BOOL WaitToRead(BOOL tryWait = false);	// Call this to gain shared read access;
											// tryWait, true to try, false to always wait;
											// if tryWait is true, return ture or false;
											// if tryWait is false, always return true.
	BOOL WaitToWrite(BOOL tryWait = false);	// Call this to gain exclusive write access
	void Done(); // Call this when done accessing the resource

private:
	CLocker	m_cs; // Permits exclusive access to other members
	COSSema	m_hsemReaders; // Readers wait on this if a writer has access
	COSSema	m_hsemWriters; // Writers wait on this if a reader has access
	int		m_nWaitingReaders; // Number of readers waiting for access
	int		m_nWaitingWriters; // Number of writers waiting for access
	int		m_nActive; // Number of threads currently with access: 0/no threads; >0/# of readers; -1/one writer

#ifdef _DEBUG
	struct LockInfo
	{
		BOOL forRead; // true, for read; false, for write.
		int count;
		LockInfo() : forRead(true), count(0) {}
	};
	typedef CQuickMap<OSThreadID, LockInfo, 8, true> ThreadIDInfoMap;
	ThreadIDInfoMap m_lockedThread;

	void AfterLock(BOOL needLock, BOOL forRead)
	{
		CNPAutoLock locker(m_cs, needLock);
		OSThreadID thrdID = OSThreadSelf();
		ThreadIDInfoMap::iterator it = m_lockedThread.find(thrdID);
		if(it == m_lockedThread.end())
		{
			LockInfo info;
			info.forRead = forRead;
			info.count = 1;
			VERIFY(m_lockedThread.insert(std::make_pair(thrdID, info)).second);
		}
		else
		{
			ASSERT(it->second.count >= 1);
			it->second.count ++;
		}
	}
	void BeforeUnlock()
	{
		OSThreadID thrdID = OSThreadSelf();
		ThreadIDInfoMap::iterator it = m_lockedThread.find(thrdID);
		ASSERT(it != m_lockedThread.end());
		ASSERT(it->second.count > 0);
		it->second.count --;
		if(0 == it->second.count)
			m_lockedThread.erase(it);
	}
#endif
};

class CNPAutoLockRead
{
	CNPAutoLockRead(const CNPAutoLockRead&);
	CNPAutoLockRead& operator = (const CNPAutoLockRead&);

	NPRWLock& _lock;
	const BOOL _isLocked;

public:
	explicit CNPAutoLockRead(NPRWLock& lock, BOOL needLock = true)
		: _lock(lock), _isLocked(needLock) { if(_isLocked) _lock.WaitToRead(); }
	~CNPAutoLockRead() { if(_isLocked) _lock.Done(); }
};

class CNPAutoUnlockRead
{
	CNPAutoUnlockRead(const CNPAutoUnlockRead&);
	CNPAutoUnlockRead& operator = (const CNPAutoUnlockRead&);

	NPRWLock& _lock;
	const BOOL _isLocked;

public:
	explicit CNPAutoUnlockRead(NPRWLock& lock, BOOL needLock = true)
		: _lock(lock), _isLocked(needLock) { if(_isLocked) _lock.Done(); }
	~CNPAutoUnlockRead() { if(_isLocked) _lock.WaitToRead(); }
};

class CNPAutoLockWrite
{
	CNPAutoLockWrite(const CNPAutoLockWrite&);
	CNPAutoLockWrite& operator = (const CNPAutoLockWrite&);

	NPRWLock& _lock;
	const BOOL _isLocked;

public:
	explicit CNPAutoLockWrite(NPRWLock& lock, BOOL needLock = true)
		: _lock(lock), _isLocked(needLock) { if(_isLocked) _lock.WaitToWrite(); }
	~CNPAutoLockWrite() { if(_isLocked) _lock.Done(); }
};

class CNPAutoUnlockWrite
{
	CNPAutoUnlockWrite(const CNPAutoUnlockWrite&);
	CNPAutoUnlockWrite& operator = (const CNPAutoUnlockWrite&);

	NPRWLock& _lock;
	const BOOL _isLocked;

public:
	explicit CNPAutoUnlockWrite(NPRWLock& lock, BOOL needLock = true)
		: _lock(lock), _isLocked(needLock) { if(_isLocked) _lock.Done(); }
	~CNPAutoUnlockWrite() { if(_isLocked) _lock.WaitToWrite(); }
};

#endif /* _NPRWLock_h_ */
